/**
 * BuzyScrollView
 *
 * 作用：一次性计算Content的大小，之后通过复用既定的几个item节点以求提高效率
 * 用法：1.在Creator面板填充该类参数；
 *      2.制作一个预制体，为其挂载名为Item的脚本，脚本中必须实现两个方法：
 *          ①   tryGetHeight：该方法用于快速计算并返回该节点高度；
 *          ②   updateItem  ：在该方法中立即改变预制体根节点的大小。
 *      3.你可以调用addBlockCallback为该scrollview拖动到顶部或底部的时候添加回调（以实现懒加载）
 *
 * 注意事项：     1.请不要在item的数量很少且未充满整个view的时候调用addItemReverse方法。
 *               2.请不要在此脚本的相关脚本中使用Layout,Widget等会动态改变大小的组件。
 */


var SCROLL_DIRECTION = {
    STAY:0,
    UP:1,
    DOWN:2
}
cc.Class({
    extends: cc.Component,

    properties: {
        ItemTemplate: {
            default: null,
            type: cc.Node
        },

        LoadingModel:{
            default:null,
            type:cc.Node,
        },

        scrollView: {
            default: null,
            type: cc.ScrollView
        },

        ItemScript:{
            default:''
        },

        firstShowCount:0,   // 首次加载的数据量

        spawnCount: 0,      // 实际创建的项数量

        spacing: 0,         // 项之间的间隔

        //降低刷新率，提升性能
        _updateTimer:0,
        _updateInterval:0.2,
    },

    onLoad: function () {

        // this.bufferZone = this.spawnCount * (this.ItemTemplate.height + this.spacing) / 2
        // this.content = this.scrollView.content
        // this.items = []
        // this.lastContentPosY = 0
        // this.bufferZone = this._getTotalHeightByItem(this.items) / 2


        // this.scrollView.node.on('scrolling',this._onScrolling,this)

        // if(this.LoadingModel)
        // {
        //     this.scrollView.node.on('scroll-ended',this.onScrollEnded,this)
        //
        //     this.scrollView.node.on('touch-up',this.onScrollUp,this)
        //
        //     this.scrollView.node.on(cc.Node.EventType.TOUCH_CANCEL,this.onTouchCancel,this)
        //
        //     this.scrollView.node.on(cc.Node.EventType.TOUCH_START,this._onTouchStart,this);
        // }


        //scroll-end:this event will be happen when the scrolling end naturally
        //or the sv scroll to top or bottom
        this.scrollView.node.on('scroll-ended',this.onScrollEnded,this)
        this.scrollView.node.on('touch-up',this.onScrollUp,this)
        this.scrollView.node.on(cc.Node.EventType.TOUCH_CANCEL,this.onTouchCancel,this)
        // this.scrollView.node.on(cc.Node.EventType.TOUCH_START,this._onTouchStart,this);
    },

    // _onTouchStart:function (event) {
    //     if(!this.scrollView.interval){
    //         event.stopPropagation()
    //     }
    // },
    //
    onScrollUp:function (event) {
        this.scrolling = true
    },

    onTouchCancel:function (event) {
        this.scrolling = true
    },

    onScrollEnded:function (event) {

        cc.log('Scroll End !')

        if(!this.scrolling){
            return
        }

        this.scrolling = false


        if(Math.abs(Math.abs(this.content.y) - Math.abs(this.view.height/2)) < 1){

            cc.log('Scroll End and BLOCKED!')

            if(this.blockCallback) this.blockCallback(true)

            // if(this.allItemInfo.length <= this.shownItemInfo.length)
            // {
            //     return
            // }

            // this.scrollView.scrollToBottom()

            // this._balanceUpAndDown()

            // this.onScrollToTop(event)

        }else {
            let topContentY = Math.round((Math.abs(Math.abs(this.content.height) - Math.abs(this.view.height/2))))
            // cc.log("topContentY:",topContentY)
            let realY = Math.round(this.content.y)
            if(realY === topContentY){
                if(this.blockCallback) this.blockCallback(false)
            }
        }
    },

    _doBalanceOnce:function () {

        for (let i = 0; i < this.items.length; ++i) {

            var nowNode = this.items[i]
            let viewPos = this.getPositionInView(nowNode)

            if (viewPos.y < -this.bufferZone) {

                var topNode = this._getTopItemNode()

                if(!this._isItemNeighbor(topNode,nowNode))
                    continue

                var nowItemScript = nowNode.getComponent(this.ItemScript)
                var preInfo = this._getNeighborInfo(topNode.getComponent(this.ItemScript).itemInfo,true)

                if(!preInfo)
                    continue

                nowItemScript.updateItem(preInfo)
                var newY = topNode.y + this.spacing + nowNode.height

                if(newY >= this.content.height)
                    continue

                nowNode.y = newY

                this._arrayTailGoHead(this.items)

                return true

            }else if (viewPos.y > this.bufferZone) {

                var bottomNode = this._getBottomItemNode()

                if(!this._isItemNeighbor(bottomNode,nowNode))
                    continue

                var nowItemScript = nowNode.getComponent(this.ItemScript)
                var nextInfo = this._getNeighborInfo(bottomNode.getComponent(this.ItemScript).itemInfo,false)

                if(!nextInfo)
                    continue

                nowItemScript.updateItem(nextInfo)
                var newY = bottomNode.y - bottomNode.height - this.spacing

                if(newY <= -this.content.height)
                    continue

                nowNode.y = newY

                this._arrayHeadGoTail(this.items)

                return true
            }
        }

        return false
    },

    _balanceUpAndDown:function () {

        var notDoneYet = true
        while (notDoneYet){
            notDoneYet = this._doBalanceOnce()
        }
    },

    // onScrollToTop:function (event) {
    //
    //     if(this._addedLoadingTop || this.waitRestoring)
    //     {
    //         return
    //     }
    //
    //     this.scrollView.vertical = false
    //
    //     this._addedLoadingTop = true
    //
    //
    //     var newLoadModel = this._addLoadingTop()
    //
    //     newLoadModel.scaleY = 0
    //
    //     var duration = 0.4
    //
    //     var scale = cc.scaleTo(duration,1,1).easing(cc.easeIn(3.0))
    //
    //     var seq = cc.sequence(scale,cc.delayTime(1),cc.callFunc(function () {
    //
    //         this._removeLoadingTop()
    //
    //         this._restoreOnce(this)
    //
    //     }.bind(this),this))
    //
    //     this.waitRestoring = true
    //
    //     newLoadModel.runAction(seq)
    //
    //     var moveBy = cc.moveBy(duration,cc.v2(0,-newLoadModel.height)).easing(cc.easeIn(3.0))
    //
    //     this.content.runAction(moveBy)
    // },

    // _removeLoadingTop:function () {
    //
    //     this._addedLoadingTop = false
    //
    //     this.content.getChildByName('loadingtop').destroy()
    // },
    //
    // _addLoadingTop:function () {
    //
    //     var newLoadModel = cc.instantiate(this.LoadingModel)
    //
    //     this.content.addChild(newLoadModel)
    //
    //     newLoadModel.name = 'loadingtop'
    //
    //     newLoadModel.position = cc.v2(0,newLoadModel.height)
    //
    //     newLoadModel.active = true
    //
    //     return newLoadModel
    // },
    //
    // _restoreOnce:function (self) {
    //
    //     var toIndex = self.shownItemInfo.length + self.restoreCount
    //
    //     var newArr = self.allItemInfo.slice(self.shownItemInfo.length,toIndex)
    //
    //     // for(var i=0;i<10;i++){
    //     //
    //     //     var chatItem = {}
    //     //     chatItem.groupId = 1
    //     //     chatItem.chatType = EnumConfig.ChatShowType.LEFT
    //     //     chatItem.contentType = EnumConfig.ChatContentType.TEXT
    //     //     chatItem.content = '我是尔康，这是来自内部的测试消息'+testCount++
    //     //
    //     //     newArr.push(chatItem)
    //     // }
    //
    //     self.addItemsReverse(newArr)
    //
    //     self.waitRestoring = false
    //
    //     self.scrollView.vertical = true
    // },

    // _onScrolling:function (event) {
    //
    //     cc.log('滚动中')
    //
    //     this.isScrolling = true
    //
    //     this.isScrollToBottom = false
    //
    //     this.isScrollToTop = false
    // },
    clearBuzyScrollView:function () {
        this.scrollView.content.removeAllChildren()
        this.scrollView.content.height = 0
        this.shownItemInfo = []
    },

    noRemoveClearBuzyScrollView:function () {
        this.scrollView.content.height = 0
        this.shownItemInfo = []
    },

    initialize: function (allItemInfo) {

        if(!allItemInfo || !allItemInfo.length){
            this.clearBuzyScrollView()
            return
        }
        this.inited = true

        // this.allItemInfo = allItemInfo

        this.shownItemInfo = allItemInfo//allItemInfo.slice(0,this.firstShowCount)
        cc.log("第一次要显示的数据：",this.shownItemInfo)

        this.content = this.scrollView.content

        this.view = cc.find('view',this.scrollView.node)


        var fromTop = true//this._checkFromTopOrBottom(this.shownItemInfo)
        cc.log("fromTop：",fromTop)

        this.items = []

        this.content.height = this._getTotalHeight(this.shownItemInfo)

        for (let i = 0; i < this.spawnCount; ++i) {

            let item = cc.instantiate(this.ItemTemplate)

            this.content.addChild(item)

            item.getComponent(this.ItemScript).itemId = i

            this.items.push(item)
        }


        if(fromTop)
        {
            this.initItemsFromTop()
        }
        else
        {
            this.initItemsFromBottom()
        }

        this.lastContentPosY = 0

        return fromTop
    },

    addBlockCallback:function (callback) {
        this.blockCallback = callback
    },
    //should be called before content height changed
    _checkFromTopOrBottom:function (allInfos) {

        var fakeTotalHeight = 0

        for(var i=0;i<allInfos.length;i++)
        {
            var info = allInfos[i]

            var height = this.ItemTemplate.getComponent(this.ItemScript).tryGetHeight(info)

            fakeTotalHeight += height

            fakeTotalHeight += this.spacing

            if(fakeTotalHeight > this.view.height)
            {
                return false
            }
        }

        return true
    },

    initItemsFromTop:function () {

        var posY = -this.spacing

        for (let i = 0; i < this.items.length; ++i) {

            let item = this.items[i]

            var info = this.shownItemInfo[i]

            if(!info)
            {
                item.active = false

                continue
            }

            item.active = true

            item.getComponent(this.ItemScript).updateItem(info)

            item.position = cc.v2(0,posY)

            posY -= (item.height + this.spacing)
        }

        this.bufferZone = this._getTotalHeightByItem(this.items) / 2
    },

    //a smaller area to fix items position
    _fixUpZoneEmpty:function () {

        for(var i=0;i<this.items.length;i++)
        {
            var itemNode = this.items[i]
            let viewPos = this.getPositionInView(itemNode)

            var doFix = viewPos.y < -this.view.height

            if (doFix) {
                var topNode = this._getTopItemNode()
                if(!this._isItemNeighbor(topNode,itemNode))
                    continue

                var nowItemScript = itemNode.getComponent(this.ItemScript)
                var preInfo = this._getNeighborInfo(topNode.getComponent(this.ItemScript).itemInfo,true)

                if(!preInfo)
                    continue

                nowItemScript.updateItem(preInfo)

                // var newY = topNode.y + topNode.height/2 + this.spacing + nowNode.height/2
                var newY = topNode.y + this.spacing + itemNode.height

                if(newY >= this.content.height)
                    continue

                itemNode.y = newY

                this._arrayTailGoHead(this.items)

                return itemNode
            }
        }

        return null
    },

    //just used for when there is still not actived node
    positionItemsFromBottom:function (botNode) {

        var startInfoIdx = this.shownItemInfo.indexOf(botNode.getComponent(this.ItemScript).itemInfo) - 1

        var posY = botNode.y + this.spacing

        let itemIdx = this.items.length - 1


        cc.log('startInfoIdx is:',startInfoIdx,itemIdx)

        for (let i = startInfoIdx; i > -1 || itemIdx > -1; i--) {

            botNode = this._getNeighborItem(botNode,true)

            var info = this.shownItemInfo[i]

            if(!botNode)
            {
                cc.warn('no item :',itemIdx)

                break
            }

            if(!info)
            {
                botNode.active = false

                break
            }

            // cc.log('info is:',info)

            botNode.active = true

            botNode.getComponent(this.ItemScript).updateItem(info)


            posY += botNode.height

            botNode.position = cc.v2(0,posY)

            posY += this.spacing
        }

        this.bufferZone = this._getTotalHeightByItem(this.items) / 2
    },

    _getNeighborItem:function (item,upOrDown) {

        if(upOrDown)
        {
            var itemIdx = this.items.indexOf(item)

            var upItem = this.items[itemIdx - 1]

            if(upItem){
                return upItem
            }else {
                return null
            }


            //下面操作会改变items数组的顺序，不要走这个逻辑了

            var preIdx = item.getComponent(this.ItemScript).itemId - 1
            if(preIdx < 0){
                preIdx = this.items.length - 1
            }

            for(var i=0;i<this.items.length;i++)
            {
                var it = this.items[i]
                if(it.getComponent(this.ItemScript).itemId == preIdx){
                    return it
                }
            }

            return null
        }
    },

    initItemsFromBottom:function () {

        var posY = -this.content.height + this.spacing

        var itemIdx = this.items.length - 1

        for (let i = this.shownItemInfo.length - 1; i > this.shownItemInfo.length - this.items.length - 1; i--) {

            let item = this.items[itemIdx--]

            var info = this.shownItemInfo[i]

            if(!info)
            {
                item.active = false

                continue
            }

            item.active = true

            item.getComponent(this.ItemScript).updateItem(info)

            posY += item.height

            item.position = cc.v2(0,posY)

            posY += this.spacing
        }

        this.bufferZone = this._getTotalHeightByItem(this.items) / 2
    },

    //Not exactely,only can be used on calculating bufferZone
    _getTotalHeightByItem:function (items) {

        var firstItem = items[0]

        var lastItem = items[items.length - 1]

        return Math.abs(lastItem.y - firstItem.y) + this.spacing + lastItem.height + this.spacing
    },

    //The scrollview would have both top and bottom margins
    _getTotalHeight:function (allShownItemInfo) {

        var totalHeight = this.spacing

        for(var i=0;i<allShownItemInfo.length;i++){
            var info = allShownItemInfo[i]

            var fakeHeight = this.ItemTemplate.getComponent(this.ItemScript).tryGetHeight(info)

            totalHeight += fakeHeight

            totalHeight += this.spacing
        }

        return totalHeight
    },

    getPositionInView: function (item) {

        let worldPos = item.parent.convertToWorldSpaceAR(item.position)

        let viewPos = this.scrollView.node.convertToNodeSpaceAR(worldPos)

        return viewPos
    },

    update: function(dt) {

        // this.updateTimer += dt
        //
        // if (this.updateTimer < this.updateInterval) {
        //     return // we don't need to do the math every frame
        // }
        //
        // this.updateTimer = 0


        if(!this.inited)
        {
            return
        }

        if(!this.scrollView.vertical)
        {
            return
        }

        //由于bufferZone的范围是从中间开始向上下延伸，而当item数量很少时，逻辑上会把仅有的item放在最上面，这会导致
        //其位置超出bufferZone的范围，从而刷新，造成意外
        if(this.content.height <= this.view.height)
            return

        let isDown = this.scrollView.content.y < this.lastContentPosY
        // this.isDown = isDown?SCROLL_DIRECTION.DOWN:SCROLL_DIRECTION.UP
        // if(this.scrollView.content.y === this.lastContentPosY){
        //     this.isDown = SCROLL_DIRECTION.STAY
        // }

        for (let i = 0; i < this.items.length; ++i) {
            var nowNode = this.items[i]
            let viewPos = this.getPositionInView(nowNode)
            if (isDown) {
                cc.log("向下")
                if (viewPos.y < -this.bufferZone) {
                    cc.log("小于可视范围")
                    var topNode = this._getTopItemNode()
                    if(!this._isItemNeighbor(topNode,nowNode))
                    {
                        continue
                    }
                    cc.log("这是顶部节点的相邻节点！")
                    var nowItemScript = nowNode.getComponent(this.ItemScript)
                    var preInfo = this._getNeighborInfo(topNode.getComponent(this.ItemScript).itemInfo,true)
                    if(!preInfo)
                    {
                        cc.log(topNode.getComponent(this.ItemScript).itemInfo,"的前面没有数据了！")
                        continue
                    }
                    cc.log("有顶部上方的数据！")
                    nowItemScript.updateItem(preInfo)
                    var newY = topNode.y + this.spacing + nowNode.height
                    if(newY >= this.content.height)
                    {
                        continue
                    }
                    nowNode.y = newY
                    this._arrayTailGoHead(this.items)
                }
            } else {
                if (viewPos.y > this.bufferZone) {
                    var bottomNode = this._getBottomItemNode()
                    if(!this._isItemNeighbor(bottomNode,nowNode))
                    {
                        continue
                    }

                    var nowItemScript = nowNode.getComponent(this.ItemScript)

                    var nextInfo = this._getNeighborInfo(bottomNode.getComponent(this.ItemScript).itemInfo,false)

                    if(!nextInfo)
                    {
                        continue
                    }

                    nowItemScript.updateItem(nextInfo)

                    // var newY = bottomNode.y - bottomNode.height/2 - this.spacing - nowNode.height/2
                    var newY = bottomNode.y - bottomNode.height - this.spacing

                    if(newY <= -this.content.height)
                    {
                        continue
                    }

                    nowNode.y = newY

                    this._arrayHeadGoTail(this.items)
                }
            }
        }

        this.lastContentPosY = this.scrollView.content.y
    },

    _getTopItemNode:function () {

        var highestNode = null
        for(var i=0;i<this.items.length;i++)
        {
            var item = this.items[i]

            if(!item.active)
                continue

            if(!highestNode){
                highestNode = item
            }else {
                highestNode = highestNode.y < item.y ? item : highestNode
            }
        }
        return highestNode
    },

    _getBottomItemNode:function () {

        var lowestNode = null
        for(var i=0;i<this.items.length;i++)
        {
            var item = this.items[i]

            if(!item.active)
                continue

            if(!lowestNode){
                lowestNode = item
            }else {
                lowestNode = lowestNode.y < item.y ? lowestNode : item
            }
        }
        return lowestNode
    },

    _isItemNeighbor:function (item1, item2) {

        var script1 = item1.getComponent(this.ItemScript)
        var script2 = item2.getComponent(this.ItemScript)

        var sub = Math.abs(script1.itemId - script2.itemId)

        if(sub === this.spawnCount - 1 || sub === 1)
        {
            return true
        }

        return false
    },

    _getNeighborInfo:function (targetInfo,preOrNext) {

        let i = this.shownItemInfo.indexOf(targetInfo)

        var nextInfo = null

        if(preOrNext)
        {
            nextInfo = this.shownItemInfo[i - 1]
        }
        else
        {
            nextInfo = this.shownItemInfo[i + 1]
        }

        if(nextInfo)
        {
            return nextInfo
        }

        return null
    },

    _arrayHeadGoTail:function (array) {
        cc.log("从头到尾喂喂喂")
        var first = array.shift()
        array.push(first)
    },

    _arrayTailGoHead:function (array) {
        cc.log("从尾到头")
        var last = array.pop()
        array.unshift(last)
    },

    addItemsReverse: function(newItems) {

        var addHeight = 0

        for(var i=0;i<newItems.length;i++)
        {
            var info = newItems[i]

            this.shownItemInfo.unshift(info)
            //
            // this.shownItemInfo.push(info)

            addHeight += this.ItemTemplate.getComponent(this.ItemScript).tryGetHeight(info)

            addHeight += this.spacing
        }

        cc.log('当前数据量：',this.shownItemInfo.length)

        cc.log('当前数据：',this.shownItemInfo)

        this.content.height += addHeight

        this.content.y += addHeight

        //move all of items down by addHeight
        for(var i=0;i<this.items.length;i++){
            var item = this.items[i]
            if(item.active){
                item.y -= addHeight
            }
        }

        // if(this.LoadingModel)
        // {
        //     this.content.y -= this.LoadingModel.height
        // }

        if(this._isInfoNotShownAll())
        {
            cc.log('Some items have not been actived,start reputting all of items')

            this.scrollView.stopAutoScroll()    //for self-comfort only

            var bottomNode = this._getBottomItemNode()

            if(!bottomNode)
            {
                cc.warn('没有显示中de节点！')
            }

            this.positionItemsFromBottom(bottomNode)
        }

        for(var i=0;i<newItems.length;i++)
        {
            if(!this._fixUpZoneEmpty())
                break
			
			cc.log('数据加载完毕，成功向上方填充一个节点')
        }
    },

    addItem: function(info) {

        var addHeight = 0

        this.shownItemInfo.push(info)

        addHeight += this.ItemTemplate.getComponent(this.ItemScript).tryGetHeight(info)

        addHeight += this.spacing

        this.content.height += addHeight

        if(this._isInfoNotShownAll())
        {
            let fromTop = this._checkFromTopOrBottom(this.shownItemInfo)

            if(fromTop)
            {
                this.initItemsFromTop()

                return true
            }
            else
            {
                this.initItemsFromBottom()

                return false
            }
        }
    },

    addItems: function(newItems) {

        var addHeight = 0

        for(var i=0;i<newItems.length;i++)
        {
            var info = newItems[i]

            this.shownItemInfo.push(info)

            addHeight += this.ItemTemplate.getComponent(this.ItemScript).tryGetHeight(info)

            addHeight += this.spacing
        }

        cc.log('当前数据量：',this.shownItemInfo.length)

        this.content.height += addHeight

        if(this._isInfoNotShownAll())
        {
            let fromTop = this._checkFromTopOrBottom(this.shownItemInfo)
            cc.log('fromTop     ',fromTop)

            if(fromTop)
            {
                this.initItemsFromTop()

                // this.scrollView.scrollToTop()

                return true
            }
            else
            {
                this.initItemsFromBottom()

                // this.scrollView.scrollToBottom()

                return false
            }
        }
    },

    _isInfoNotShownAll:function () {

        for(var i=0;i<this.items.length;i++) {

            var itemNode = this.items[i]

            var info = this.shownItemInfo[i]

            if (info && !itemNode.active)
            {
                return true
            }
        }

        return false
    },

    removeItem: function() {

        if (this.totalCount - 1 < 30) {
            cc.error("can't remove item less than 30!")
            return
        }

        this.content.height = (this.totalCount - 1) * (this.ItemTemplate.height + this.spacing) + this.spacing // get total content height
        this.totalCount = this.totalCount - 1
    },

    // scrollEvent: function(sender, event) {
    //     switch(event) {
    //         case 0:
    //             this.lblScrollEvent.string = "Scroll to Top";
    //             break;
    //         case 1:
    //             this.lblScrollEvent.string = "Scroll to Bottom";
    //             break;
    //         case 2:
    //             this.lblScrollEvent.string = "Scroll to Left";
    //             break;
    //         case 3:
    //             this.lblScrollEvent.string = "Scroll to Right";
    //             break;
    //         case 4:
    //             this.lblScrollEvent.string = "Scrolling";
    //             break;
    //         case 5:
    //             this.lblScrollEvent.string = "Bounce Top";
    //             break;
    //         case 6:
    //             this.lblScrollEvent.string = "Bounce bottom";
    //             break;
    //         case 7:
    //             this.lblScrollEvent.string = "Bounce left";
    //             break;
    //         case 8:
    //             this.lblScrollEvent.string = "Bounce right";
    //             break;
    //         case 9:
    //             this.lblScrollEvent.string = "Auto scroll ended";
    //             break;
    //     }
    // },

});
